package baseStructure;

/***
 * @author Ragnvald Fuglestein
 */

import java.io.Serializable;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.RandomAccess;

import callback.ElementChanged;

public class BaseList<E> implements List<E>, RandomAccess, Cloneable, Serializable, ElementChanged {
	private static final long serialVersionUID = 7413551781707861707L;

	private List<BaseListListener> changeListeners = new LinkedList<>();
	public void addChangeListener(BaseListListener listener) {
		changeListeners.add(listener);
	}
	public boolean removeChangeListener(BaseListListener listener) {
		return changeListeners.remove(listener);
	}
	
	protected void notifyChangeListeners(Object o) {
		for (BaseListListener listener : changeListeners) {
			listener.baseListChanged(o);
		}
	}
	
	private List<BaseListListener> insertListeners = new LinkedList<>();
	public void addInsertListener(BaseListListener listener) {
		insertListeners.add(listener);
	}
	public boolean removeInsertListener(BaseListListener listener) {
		return insertListeners.remove(listener);
	}
	
	protected void notifyInsertListeners(Object o) {
		for (BaseListListener listener : insertListeners) {
			listener.baseListInserted(o);
		}
	}

	
	private List<BaseListListener> removeListeners = new LinkedList<>();
	public void addRemoveListener(BaseListListener listener) {
		removeListeners.add(listener);
	}
	public boolean removeRemoveListener(BaseListListener listener) {
		return removeListeners.remove(listener);
	}
	
	protected void notifyRemoveListeners(Object o) {
		for (BaseListListener listener : removeListeners) {
			listener.baseListRemoved(o);
		}
	}

	
	private List<BaseListListener> structureListeners = new LinkedList<>();
	public void addStructureListener(BaseListListener listener) {
		structureListeners.add(listener);
	}
	public boolean removeStructureListener(BaseListListener listener) {
		return structureListeners.remove(listener);
	}
	
	protected void notifyStructureListeners() {
		for (BaseListListener listener : structureListeners) {
			listener.baseListStructureChanged();
		}
	}

	@Override public void elementChanged(Object o) {
		notifyChangeListeners(o);
	}
	
	private int size = 0;
	private int capacity;
	private int increment;
	private int initialCapacity;
	private int modcount;
	private Object[] data;
	
	public final void clear (int initialSize, int increment) {
		if (initialSize <= 0) {
			throw new IllegalArgumentException("Illegal initialSize(" + initialSize + ")");
		}
		this.capacity = initialSize;
		this.initialCapacity = initialSize;
		this.increment = increment;
		this.data = new Object[capacity];
		modcount = 0;
		notifyStructureListeners();
	}

	public BaseList() {
		clear(10, -1);
	}
	
	public BaseList(int s) throws IllegalArgumentException {
		clear(s, 10);
	}

	public BaseList(int s, int g) throws IllegalArgumentException {
		clear(s, g);
	}
	
	public final void trimToSize() {
		if (size < capacity && size < initialCapacity) {
			waitForBlock();
			Object[] old = data;
			data = new Object[size];
			for (int i = 0; i < capacity; i++) {
				if (old[i] != null) {
					data[i] = old[i];
				}
			}
			capacity = size;
			removeBlock();
		}
	}
	
	@Override public final boolean contains(Object obj) {
		return indexOf(obj) != -1;
	}
	
	protected final void grow() {
		Object[] old = data;
		
		if (increment != -1) capacity += increment;
		else capacity *= 2 + 1;
		data = new Object[capacity];
		
		for (int i = 0; i < size; i++) {
			if (old[i] != null) {
				data[i] = old[i];
			}
		}
	}
	
	@Override public int indexOf(Object o) {
		for (int i = 0; i < size; i++) {
			if (o.equals(data[i])) {
				return i;
			}
		}
		return -1;
	}
	
	@Override public void add(int position, E obj) {
		if (obj == null) throw new RuntimeException("Invalid object");
		waitForBlock();
		if (size == capacity) grow();
		if (position >= size) {
			data[size] = obj;
		} else {
			for (int i = size; i > position; i--) {
				data[i] = data[i - 1];
			}
			data[position] = obj;
		}
		size++;
		removeBlock();
		modcount++;
		notifyInsertListeners(obj);
	}
	
	@Override public boolean add(E obj) {
		waitForBlock();
		if (size == capacity) grow();
		data[size++] = obj;
		removeBlock();
		modcount++;
		notifyInsertListeners(obj);
		return true;
	}
	
	@Override public boolean addAll(Collection<? extends E> obj) {
		boolean ret = false;
		for (E o : obj) {
			if (add(o)) ret = true;
		}
		return ret;
	}

	@SuppressWarnings("unchecked")
	@Override public final E get (int index) {
		if (index < 0 || index >= size) {
			throw new IndexOutOfBoundsException();
		}
		return (E) data[index];
	}

	@SuppressWarnings("unchecked")
	@Override public final Object[] toArray() {
		return (E[]) data;
	}
	
	@Override public final boolean remove(Object obj) {
		int index = indexOf(obj);
		if (index == -1) return false;
		remove(index);
		return true;
	}
	
	@SuppressWarnings("unchecked")
	@Override public E remove(int index) {
		if (index < 0 || index >= size) throw new IndexOutOfBoundsException();
		waitForBlock();
		E obj = (E) data[index];
		size--;
		for (int i = index; i < size; i++) data[i] = data[i+1];
		data[size] = null;
		removeBlock();
		modcount++;
		notifyRemoveListeners(obj);
		return obj;
	}
	
	@Override public final int size() {
		return size;
	}
	
	@Override public final boolean isEmpty() {
		return size == 0;
	}
	
	public void move(int oldPos, int newPos) {
		if (oldPos == newPos) return;
		if (oldPos < 0 || oldPos >= size || newPos >= size) throw new IndexOutOfBoundsException();
		waitForBlock();
		Object obj = data[oldPos];
		if (oldPos < newPos) {
			for (int i = oldPos; i < newPos; i++) {
				data[i] = data[i+1];
			}
			data[newPos] = obj;
		} else if (oldPos > newPos) {
			for (int i = oldPos; i > newPos; i--) {
				data[i] = data[i - 1]; 
			}
			data[newPos] = obj;
		}
		removeBlock();
		modcount++;
		notifyStructureListeners();
	}
	@Override public Iterator<E> iterator() {
		//waitForBlock();
		return new Itr();
	}
	
	private int blocking = 0;
	
	private void waitForBlock() {
		while (blocking > 0) {
			try {
				//Log.d("Blocked list: " + blocking);
				Thread.sleep(10);
			} catch (InterruptedException e) { } 
		}
		blocking++;
	}
	
	private void removeBlock() {
		blocking--;
	}
	
	private class Itr implements Iterator<E> {
		int next = 0;
		int expectedmodcount = modcount;
		@Override public boolean hasNext() {
			return (next != size);
		}

		@SuppressWarnings("unchecked")
		@Override public E next() {
			if (expectedmodcount != modcount) throw new ConcurrentModificationException();
			return (E) data[next++];
		}

		@Override public void remove() { throw new UnsupportedOperationException(); }	
	}

	@Override public void clear() {
		waitForBlock();
		clear(initialCapacity, increment);
		removeBlock();
	}

	@Override public boolean containsAll(Collection<?> c) {
		for (Object o : c) if (indexOf(o) == -1) return false;
		return true;
	}

	@Override public boolean removeAll(Collection<?> c) {
		boolean ret = false;
		for (Object o : c) if (remove(o)) ret = true;
		return ret;
	}

	@Override public boolean retainAll(Collection<?> c) {
		boolean ret = false;
		for (Object o : data) if (!c.contains(o)) {
			ret = true;
			remove(o);
		}
		return ret;
	}

	@SuppressWarnings({ "unchecked", "hiding" })
	@Override public <E> E[] toArray(E[] a) {
		return (E[]) data;
	}

	public String toString() {
		String str = "";
		for (int i = 0; i < size; i++) {
			str += data[i] + "\n";
		}
		return str;
	}

	public static void main(String[] args) {
		BaseList<String> str = new BaseList<>(2,2);

		str.add("0");
		System.out.println(str.toString());

		str.add("1");
		str.add("2");
		System.out.println(str.toString());
		

		str.remove("2");
		str.add(0,"0/4");
		str.add("5");
		System.out.println(str.toString());
	}

	@Override
	public boolean addAll(int index, Collection<? extends E> c) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int lastIndexOf(Object o) {
		throw new UnsupportedOperationException();
	}

	@Override
	public ListIterator<E> listIterator() {
		throw new UnsupportedOperationException();
	}

	@Override
	public ListIterator<E> listIterator(int index) {
		throw new UnsupportedOperationException();
	}

	@Override
	public E set(int index, E element) {
		throw new UnsupportedOperationException();
	}

	@Override
	public List<E> subList(int fromIndex, int toIndex) {
		throw new UnsupportedOperationException();
	}
}
